// PanasonicDriver.cs
//
// Implements HomeUX.Components.PanasonicDriver.
//
// See DocFiles\PanasonicDriver.htm for driver usage information.
//

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Linq;
using System.Xml;
using HomeUX.DriverModel;
using HomeUX.Networking;
using HomeUX.Synchronization;
using HomeUX.Utilities;
using System.Text;

namespace HomeUX.Components
{

/// <summary>
/// Implements a HomeUX driver that accesses the telephone log of a Panasonic D816 Digital Super
/// Hybrid System telephone PBX connected via a serial port.
/// </summary>
///
public class PanasonicDriver : Driver
{
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Private Fields
    //

    /// <summary>
    /// Maps each telephone extension number (referred to as an "ID" here) to the corresponding
	/// <r>Extension</r> object, as specified in the .config file.
    /// </summary>
    Dictionary<int, Extension> _mapIdToExtension;

    /// <summary>
    /// The serial port specification string that defines the virtual or real serial port to open.
    /// </summary>
    string _serialPortSpec;

    /// <summary>
    /// Provides device-specific hardware access.
    /// </summary>
    PanasonicHardware _hardware;

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Device Class Overrides & Related Methods
    //

    /// <summary>
    /// Overrides <r>Driver.OnInitialize</r>.
    /// </summary>
    ///
    protected override void OnInitialize()
    {
        // read attributes of the "<Device>" XML element
        _serialPortSpec = Configuration.GetRequiredAttribute("SerialPort");

        // initialize data structures
        _mapIdToExtension = new Dictionary<int, Extension>();

        // read attributes of child XML element
        foreach (ConfigurationElement element in Configuration.Children)
        {
            switch (element.Name)
            {

            case "Extension":

                // set <extension> to the "<Extension>" element
                var extension = new Extension()
                {
                    Id = element.GetRequiredIntAttribute("Id"),
                    Name = element.GetRequiredAttribute("Name"),
                };

                // ensure that <extension.Id> hasn't already been taken by a previous
				// "<Extension>" element, and add <extension> to <_mapIdToExtension>
                try
                {
                    _mapIdToExtension.Add(extension.Id, extension);
                }
                catch (ArgumentException)
                {
                    throw new ConfigurationException(element,
						"Duplicate extension number: {0}", extension.Id);
                }
                break;

            }
        }
    }

    /// <summary>
    /// Overrides <r>Driver.OnEnable</r>.
    /// </summary>
    ///
    protected override void OnEnable()
    {
        // enable occurs asynchronously -- queue an PanasonicEnableRequest to perform this operation
        QueueRequest(new PanasonicEnableRequest() { Driver = this }, false);
    }

    /// <summary>
    /// Overrides <r>Driver.OnDisable</r>.
    /// </summary>
    ///
    protected override void OnDisable()
    {
        // disable occurs synchronously -- perform it now
        CloseHardware();
        DisableCompleted();
    }

	///////////////////////////////////////////////////////////////////////////////////////////////
	// IDisposable Overrides
	//

    /// <summary>
    /// Implements <n>IDisposable.Dispose</n>.
    /// </summary>
    ///
    public override void Dispose()
    {
        if (_hardware != null)
        {
            _hardware.Dispose();
            _hardware = null;
        }
        base.Dispose();
    }

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Other Methods
	//

    /// <summary>
    /// Assuming <r>_hardware</r> is null, this method creates an <r>PanasonicHardware</r> object,
	/// used to access Panasonic hardware, and sets <r>_hardware</r> to refer to it.
    /// </summary>
	///
    void OpenHardware()
    {
        VirtualSerialPort serialPort = null;
        try
        {
            // set <serialPort> to a new serial port, as specified by <_serialPortSpec>
            serialPort = VirtualSerialPort.GetPort(_serialPortSpec);

            // set <_hardware> to a newly-allocated PanasonicHardware object that will use
			// <serialPort> to communicate with the hardware
            _hardware = new PanasonicHardware(WorkQueue, serialPort, Name);

            // hook up <_hardware> events
            // IMPORTANT: do corresponding "-=" in CloseHardware
            _hardware.PhoneCallReported += OnHardwarePhoneCallReported;
            _hardware.WarningsReported += OnHardwareWarningsReported;
            // IMPORTANT: do corresponding "-=" in CloseHardware

            // open <_hardware>; this will fail if the serial port cannot be opened
            _hardware.Open();
        }
        finally
        {
            // if <serialPort> was allocated but ownership of it wasn't transferred to <_hardware>,
			// free <serialPort> to prevent a memory leak
            if ((serialPort != null) && (_hardware == null))
            {
                serialPort.Dispose();
                serialPort = null;
            }
        }

    }

    /// <summary>
    /// Assuming <r>_hardware</r> is not null, this method frees it.
    /// </summary>
    ///
    void CloseHardware()
    {
        // disconnect <_hardware> events
        _hardware.PhoneCallReported -= OnHardwarePhoneCallReported;
        _hardware.WarningsReported -= OnHardwareWarningsReported;

		// dispose of <_hardware>
        _hardware.Dispose();
        _hardware = null;
    }

    /// <summary>
    /// See <r>PanasonicHardware.PhoneCallReported</r>.
    /// </summary>
    ///
    /// <param name="sender">See <r>PanasonicHardware.PhoneCallReported</r>.</param>
    ///
    /// <param name="e">See <r>PanasonicHardware.PhoneCallReported</r>.</param>
    ///
    public void OnHardwarePhoneCallReported(object sender,
		PanasonicHardwarePhoneCallReportedEventArgs e)
    {
        // report the phone call to clients
        var message = new StringBuilder();
        message.AppendFormat(
            (e.IsIncoming ? "Incoming phone call from {0}" : "Outgoing phone call to {0}"),
            e.PhoneNumber);
        if (!String.IsNullOrEmpty(e.CallerId))
            message.AppendFormat(" ({0})", e.CallerId);
        Extension extension;
        message.AppendFormat(": extension {0}", e.Extension);
        _mapIdToExtension.TryGetValue(e.Extension, out extension);
        if (extension != null)
            message.AppendFormat(" ({0})", extension.Name);
        if (e.RingDuration > TimeSpan.Zero)
            message.AppendFormat(", ring {0}", FormatDuration(e.RingDuration));
        message.AppendFormat(", call {0}", FormatDuration(e.CallDuration));
        ReportAlert(MessageAlertType.Information, message.ToString());
    }

    /// <summary>
    /// Formats a TimeSpan value representing a duration of a phone call, ring, etc.
    /// </summary>
    ///
    /// <param name="duration">The duration to format.</param>
    ///
    static string FormatDuration(TimeSpan duration)
    {
        int totalSeconds = (int)duration.TotalSeconds;
        int minutes = totalSeconds / 60;
        int seconds = totalSeconds % 60;
        return String.Format("{0}:{1:00}", minutes, seconds);
    }

    /// <summary>
    /// See <r>PanasonicHardware.WarningsReported</r>.
    /// </summary>
    ///
    /// <param name="sender">See <r>PanasonicHardware.WarningsReported</r>.</param>
    ///
    /// <param name="e">See <r>PanasonicHardware.WarningsReported</r>.</param>
    ///
    public void OnHardwareWarningsReported(object sender,
		PanasonicHardwareWarningsReportedEventArgs e)
    {
        foreach (string message in e.Messages)
            ReportAlert(MessageAlertType.Warning, message);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // PanasonicRequest and Derived Types
    //

    /// <summary>
    /// A request from a client.
    /// </summary>
    ///
    abstract class PanasonicRequest : Driver.Request
    {
        /// <summary>
        /// The driver that owns this request.
        /// </summary>
        protected new PanasonicDriver Driver
        {
            [DebuggerStepThrough]
            get
            {
                return (PanasonicDriver)base.Driver;
            }
        }
    }

    /// <summary>
    /// A requst to enable the device.
    /// </summary>
    ///
    [DebuggerDisplay("Enable driver")]
    class PanasonicEnableRequest : PanasonicRequest, Driver.IEnableRequest
    {
        /// <summary>
        /// Overrides <r>StateMachine.Implementation</r>.
        /// </summary>
        ///
        protected override IEnumerable<bool> Implementation()
        {
            // open the hardware
            try
            {
                Driver.OpenHardware();
            }
            catch (VirtualSerialPortException ex)
            {
                Error = Util.FormatExceptionMessage(ex);
                yield break; // end JdsRequest
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Other Nested Types
    //

    /// <summary>
    /// Represents an extension to the TV.
    /// </summary>
    ///
    [DebuggerDisplay("{DebuggerDisplay}")]
    class Extension
    {
        /// <summary>
        /// The telephone extension number (e.g. 101).
        /// </summary>
        public int Id;

        /// <summary>
        /// The name of this extension.
        /// </summary>
        public string Name;

        /// <summary>
        /// Returns a value used by <c>[DebuggerDisplay]</c>, encapsulated to prevent the debugger
        /// from displaying it in quotes.
        /// </summary>
        public object DebuggerDisplay
        {
            get
            {
                return new EncapsulatedString("Extension \"{0}\" (#{1})", Name, Id);
            }
        }
    }
}

}

